import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;

/**
 * This class orders the records in an input 490db file and writes the result to
 * an output file. It does this by writing a C++ program that it compiles and
 * executes to do the ordering.
 * 
 * @author Jonathan
 * 
 */
public class Order {

	private String attsFile;
	private String writeBufferFile;
	private String readInFile;
	private String writeOutFile;
	private String sortFunctionsFile;
	private String compareFile;
	private String queryExecutableFile;

	// JES TODO: This comment is completely wrong
	/**
	 * attsInInputFile is the list, in order, of all of the attributes (and
	 * their types) that are present in the input file.
	 * 
	 * attsInOutputFile is the list, in order, of all of the attributes (and
	 * types) that are going to be written to the output file. This includes
	 * attributes that are the result of an aggregate computation.
	 * 
	 * groupingAtts is a list of all of the attributes that are used to perform
	 * the grouping.
	 * 
	 * exprsToComputeOutputAtts is a map that lists, for each of the attributes
	 * in attsInOutputFile, the expression that will be used to produce that
	 * attribute. For example, an entry in the map might be ["outAtt2", ["sum",
	 * "o_totalprice * Float (4.0)"]]. This means that outAtt2 is the result of
	 * a "sum" aggregate over the expression "o_totalprice * Float (4.0)". In
	 * place of "sum" could be a "none" or an "avg". In the case of "none", no
	 * aggregation is performed. Note that in the case of "none", the expression
	 * should be a function of only the grouping atts. In the case of "avg", an
	 * average is computed.
	 * 
	 * Then we have the name of the input file that goes to the grouping, the
	 * name of the output file that should result from the grouping, the name of
	 * the C++ compiler (this will typically be "g++" on a Linux/Mac system),
	 * and finally the path of the directory where all of the C++ files are
	 * located, and where the created C++ files are supposed to be written to
	 * (this might be something like "cppFiles/"). Make sure to put a slash on
	 * there so that we can just append the file name to the end of the
	 * directory (if you are using windows, the slash goes the other way!).
	 * 
	 */
	/**
	 * 
	 * 
	 * @param attsInInputFile
	 * @param attsInOutputFile
	 * @param exprsToComputeOutputAtts
	 * @param sortKeyExpressions
	 * @param inFile name of input file
	 * @param outFile name of output file
	 * @param compiler name of the compiler program, e.g. g++
	 * @param cppDir
	 * @throws IOException
	 */
	public Order(ArrayList<Attribute> attsInInputFile, ArrayList<Attribute> attsInOutputFile,
			Map<String, String> exprsToComputeOutputAtts, ArrayList<SortKeyExpression> sortKeyExpressions,
			String inFile, String outFile, String compiler, String cppDir) throws IOException
	{

		// Declare the input record attributes in Atts.cc
		attsFile = cppDir + "Atts.cc";
		FileWriter fstream = new FileWriter(attsFile);
		BufferedWriter out = new BufferedWriter(fstream);
		for(Attribute i : attsInInputFile)
		{
			out.write(i.getType() + " " + i.getName() + ";\n");
		}
		out.close();
		
		// Write code for the temporary buffer if we are doing a two pass algorithm
		if (DBConfig.TWO_PASS_ALGO)
		{
			writeBufferFile = cppDir + "WriteBuffer.cc";
			fstream = new FileWriter(writeBufferFile);
			out = new BufferedWriter(fstream);
			for(Attribute i : attsInInputFile)
			{
				out.write(i.getName() + ".WriteOut(toMe);\n");
			}
			out.close();
		}

		// Write code to read a single input record in readIn.cc
		readInFile = cppDir + "ReadIn.cc";
		fstream = new FileWriter(readInFile);
		out = new BufferedWriter(fstream);
		for(Attribute i : attsInInputFile)
		{
			out.write("res = " + i.getName() + ".ReadIn(fromMe);\n");
		}
		out.close();

		// Write code to write the output attributes in WriteOut.cc
		writeOutFile = cppDir + "WriteOut.cc";
		fstream = new FileWriter(writeOutFile);
		out = new BufferedWriter(fstream);
		for(Attribute i : attsInOutputFile)
		{
			String type = i.getType();
			String name = i.getName();
			String exp = exprsToComputeOutputAtts.get(name);
			if(exp == null)
			{
				throw new RuntimeException("could not find an expression for att " + i.getName());
			}
			out.write(type + " " + name + " = " + exp + ";\n" + name + ".WriteOut(toMe);\n");
		}
		out.close();
		
		// Write code to compute the sorting functions in SortFunctions.cc
		sortFunctionsFile = cppDir + "SortFunctions.cc";
		fstream = new FileWriter(sortFunctionsFile);
		out = new BufferedWriter(fstream);
		int sortKeyCount = sortKeyExpressions.size();
		for(int i = 0; i < sortKeyCount; i++)
		{
			SortKeyExpression ske = sortKeyExpressions.get(i);
			String dataType = ske.getDataType();
			String executionExp = ske.getExecutionString();
			out.write(dataType + " sortFunction" + i + "() const { return " + executionExp + "; }\n");
		}
		out.close();

		// Write code to compare for sorting
		compareFile = cppDir + "Compare.cc";
		fstream = new FileWriter(compareFile);
		out = new BufferedWriter(fstream);
		for(int i = 0; i < sortKeyCount; i++)
		{
			boolean asc = sortKeyExpressions.get(i).isAsc();
			out.write("if (sortFunction" + i + "() " + (asc ? "<" : ">") + " r2.sortFunction" + i + "()) return true;\n");
			out.write("if (sortFunction" + i + "() " + (asc ? ">" : "<") + " r2.sortFunction" + i + "()) return false;\n");
		}
		out.close();

		// now we compile the code
		try
		{
			Runtime rt = Runtime.getRuntime();
//			queryExecutableFile = cppDir + "a.out";
			queryExecutableFile = cppDir + "Order";
			String cppFile = (DBConfig.TWO_PASS_ALGO) ? "TPOrderBy.cc" : "OrderBy.cc";
			String cmdarr[] = { compiler, "-O3", "-o" + queryExecutableFile, "-Wno-write-strings",
					cppDir + cppFile };
			Process proc = rt.exec(cmdarr);
			int exitVal = proc.waitFor();
			if(exitVal != 0)
			{
				System.out.println("When I tried to compile the order operation using " + compiler
						+ ", I got an error. " + " Try running the command:\n\n" + compiler + " -o "
						+ queryExecutableFile + " -Wno-write-strings " + cppDir
						+ cppFile + "\n\nyourself from the command line" + " to see what happened.\n");
				throw new RuntimeException("compilation failed");
			}

		}
		catch(Throwable t)
		{
			throw new RuntimeException(t);
		}

		// and we run it
		try
		{
			Runtime rt = Runtime.getRuntime();
			String cmdarr[] = { queryExecutableFile, inFile, outFile };
			Process proc = rt.exec(cmdarr);
			int exitVal = proc.waitFor();
			if(exitVal != 0)
			{
				throw new RuntimeException("OrderBy: I could not manage to run the compiled program. exitVal = " + exitVal);
			}
		}
		catch(Throwable t)
		{
			throw new RuntimeException(t);
		}

		// Delete temporary cpp files.
		// TODO: Come back and look at this later. Need to make sure that temporary
		// table files are getting deleted as well.
		if(DBConfig.DELETE_TEMP_CPP_FILES)
		{
			try
			{
				for(String filename : new String[] { attsFile, readInFile, writeOutFile, sortFunctionsFile, compareFile, queryExecutableFile })
				{
					if(filename == null || filename.endsWith(".tbl"))
					{
						continue;
					}

					File f = new File(filename);
					if(f.exists() && f.isFile())
					{
						f.delete();
					}
				}
			}
			catch(Throwable t)
			{
				throw new RuntimeException(t);
			}
		}
	}
}
